/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package com.thienloc.omap.controller.broadcast;

import com.google.common.collect.Maps;
import com.google.common.util.concurrent.Uninterruptibles;
import com.thienloc.omap.controller.BaseBean;
import com.thienloc.omap.entity.BlackList;
import com.thienloc.omap.entity.Prefix;
import com.thienloc.omap.jdbc.dao.TblSmsBroadcastingDao;
import com.thienloc.omap.jdbc.dao.TblTmpDao;
import com.thienloc.omap.jdbc.entity.TblSmsBroadcasting;
import com.thienloc.omap.service.broadcast.BroadcastService;
import com.thienloc.omap.session.BlackListFacade;
import com.thienloc.omap.session.PrefixFacade;
import com.thienloc.omap.util.CommonUtil;
import com.thienloc.omap.util.Constant;
import com.thienloc.omap.util.JsfUtil;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import javax.annotation.PostConstruct;
import javax.ejb.EJB;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 *
 * @author GiangLT
 */
@ManagedBean
@ViewScoped
public class ShootingBean extends BaseBean implements Serializable {

    @EJB
    private PrefixFacade prefixFacade;
    @EJB
    private BlackListFacade blackListFacade;
    private String msisdn;
    private String content;
    private String shortCode;
    private String prefix = "";
    private String suffix = "";
    private Integer limitNum;
    private Integer speed;
    private boolean excludeBlackList;
    private boolean insertNewDb;
    private final Logger logger = LoggerFactory.getLogger(ShootingBean.class);
    private BlockingQueue<String> pendingMdns;
    private BlockingQueue<String> sentSuccessMdns;
    private BlockingQueue<String> sentFailedMdns;
    private BlockingQueue<TblSmsBroadcasting> tblSmsBroadcastingQueue;
    private BlockingQueue<TblSmsBroadcasting> tblTmpQueue;
    private ExecutorService executor;
    private final TblSmsBroadcastingDao tblSmsBroadcastingDao = new TblSmsBroadcastingDao();
    private final TblTmpDao tblTmpDao = new TblTmpDao();
    private final int BATCH_SIZE = 500;

    @PostConstruct
    public void init() {
        try {
            Properties properties = new Properties();
            File file = new File(Constant.CONFIG_FOLDER + Constant.CONFIG_FILE_NAME);
            properties.load(new FileReader(file));

            limitNum = Integer.valueOf(properties.getProperty("broadcast.limitNum", "5"));
            speed = Integer.valueOf(properties.getProperty("broadcast.speed", "9"));
            excludeBlackList = "true".equalsIgnoreCase(properties.getProperty("broadcast.excludeBlackList", "false"));
            insertNewDb = "true".equalsIgnoreCase(properties.getProperty("broadcast.insertNewDb", "false"));
            if (properties.getProperty("broadcast.includeSuffix", "false").equalsIgnoreCase("true")) {
                suffix = properties.getProperty("broadcast.suffix", "");
            }

            if (properties.getProperty("broadcast.includePrefix", "false").equalsIgnoreCase("true")) {
                prefix = properties.getProperty("broadcast.prefix", "");
            }

        } catch (IOException ex) {
            JsfUtil.addErrorMessage("Lỗi", ex.getMessage());
            logger.error("[{}]: ", getUsername(), ex);
        }

    }

    public void sendMultiSms() {
        if (content.contains(";") || content.contains(";")) {
            JsfUtil.addErrorMessage("Sai nội dung", "Nội dung tin nhắn không được chứa dấu ';' hoặc dấu '|'");
            return;
        }

        try {
            String[] mdns = msisdn.split("\\n");
            logger.warn("[{}]: Shooting to {} customers", getUsername(), mdns.length);

            if (mdns.length > limitNum) {
                JsfUtil.addErrorMessage("Vượt số lượng", "Số lượng khách hàng được phép gửi tin ko được quá " + limitNum);
                return;
            }

            if (excludeBlackList) {
                pendingMdns = removeBlacklist(mdns);
            } else {
                pendingMdns = new ArrayBlockingQueue<String>(mdns.length);
                for (String mdn : mdns) {
                    pendingMdns.add(CommonUtil.normalizeMsisdn(mdn.trim().replaceAll("\\r", "")));
                }
            }
            sentSuccessMdns = new ArrayBlockingQueue<String>(mdns.length);
            sentFailedMdns = new ArrayBlockingQueue<String>(mdns.length);
            tblSmsBroadcastingQueue = new ArrayBlockingQueue<TblSmsBroadcasting>(mdns.length);
            tblTmpQueue = new ArrayBlockingQueue<TblSmsBroadcasting>(mdns.length);

            content = prefix + content + suffix;
            List<Prefix> prefixes = prefixFacade.findAll();
            final Map<String, Prefix> prefixMap = Maps.newHashMap();
            for (Prefix mdnPrefix : prefixes) {
                prefixMap.put(mdnPrefix.getPrefix(), mdnPrefix);
            }

            executor = Executors.newCachedThreadPool();

            Runnable shooting = new Runnable() {

                @Override
                public void run() {
                    BroadcastService broadcastService = new BroadcastService();
                    try {

                        broadcastService.connect();
                        int cnt = 0;
                        CountDownLatch countDownLatch = new CountDownLatch(pendingMdns.size());
                        while (pendingMdns != null && !pendingMdns.isEmpty()) {
                            ShootingTask shootingTask = new ShootingTask(prefixMap, pendingMdns.poll(), broadcastService, countDownLatch);
                            executor.execute(shootingTask);
                            if (++cnt >= speed) {
                                Uninterruptibles.sleepUninterruptibly(1, TimeUnit.SECONDS);
                                cnt = 0;
                            }
                        }

                        countDownLatch.await();

                        JsfUtil.addSuccessMessage("Thành công!", "Đã gửi tin nhắn tới " + pendingMdns.size() + " số ");
                        executor.shutdown();
                        List<TblSmsBroadcasting> insertList = new ArrayList<TblSmsBroadcasting>();
                        tblSmsBroadcastingQueue.drainTo(insertList);
                        tblSmsBroadcastingDao.add(insertList);

                        if (insertNewDb) {
                            insertList = new ArrayList<TblSmsBroadcasting>();
                            tblTmpQueue.drainTo(insertList);
                            tblTmpDao.add(insertList);
                        }

                    } catch (Exception ex) {
                        logger.error("[{}]: ", getUsername(), ex);
                        JsfUtil.addErrorMessage("Lỗi!", "Có lỗi xảy ra trong quá trình gửi tin");
                    } finally {
                        try {
                            broadcastService.disconnect();
                        } catch (IOException ex) {
                            logger.error("[{}]: ", getUsername(), ex);
                        }
                    }
                }
            };

            executor.submit(shooting);

        } catch (Exception ex) {
            logger.error("[{}]: ", getUsername(), ex);
            JsfUtil.addErrorMessage("Lỗi!", "Có lỗi xảy ra trong quá trình gửi tin");
        }
    }

    public void stop() {
        List<Runnable> shutdownNow = executor.shutdownNow();
    }

    private class ShootingTask implements Runnable {

        private final Map<String, Prefix> prefixMap;
        private final String mdnStr;
        private final BroadcastService broadcastService;
        private final CountDownLatch countDownLatch;

        public ShootingTask(Map<String, Prefix> prefixMap, String mdnStr, BroadcastService broadcastService, CountDownLatch countDownLatch) {
            this.prefixMap = prefixMap;
            this.mdnStr = mdnStr;
            this.broadcastService = broadcastService;
            this.countDownLatch = countDownLatch;
        }

        @Override
        public void run() {
            try {
                Prefix validPrefix = prefixMap.get(CommonUtil.getPrefix(mdnStr));

                if (validPrefix != null) {
                    StringBuilder sb = new StringBuilder();
                    int seq = sessionBean.getBroadcastInstantSeq();
                    sb.append(seq).append(";");
                    sb.append(mdnStr).append(";");
                    sb.append(shortCode).append(";");

                    sb.append(content).append(";");
                    sb.append("smsgate_").append(validPrefix.getOperator()).append("6771");
                    sb.append("|");

                    broadcastService.send(sb.toString());
                    sentSuccessMdns.put(mdnStr);
                    TblSmsBroadcasting tblSmsBroadcasting = new TblSmsBroadcasting();
                    tblSmsBroadcasting.setSc(shortCode);
                    tblSmsBroadcasting.setMsisdn(mdnStr);
                    tblSmsBroadcasting.setProvider("smsgate_" + validPrefix.getOperator() + "6771");
                    tblSmsBroadcasting.setResult("OK");
                    tblSmsBroadcasting.setSms(content);
                    tblSmsBroadcasting.setSentTime(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
                    tblSmsBroadcastingQueue.offer(tblSmsBroadcasting);
                    if (tblSmsBroadcastingQueue.size() >= BATCH_SIZE) {
                        List<TblSmsBroadcasting> insertList = new ArrayList<TblSmsBroadcasting>(BATCH_SIZE);
                        tblSmsBroadcastingQueue.drainTo(insertList, BATCH_SIZE);
                        tblSmsBroadcastingDao.add(insertList);
                    }

                    if (insertNewDb) {
                        tblTmpQueue.offer(tblSmsBroadcasting);
                        if (tblTmpQueue.size() >= BATCH_SIZE) {
                            List<TblSmsBroadcasting> insertList = new ArrayList<TblSmsBroadcasting>(BATCH_SIZE);
                            tblTmpQueue.drainTo(insertList, BATCH_SIZE);
                            tblTmpDao.add(insertList);
                        }
                    }

                }
            } catch (Exception ex) {
                try {
                    sentFailedMdns.put(mdnStr);
                    logger.error("[{}]: ", getUsername(), ex);
                    JsfUtil.addErrorMessage("Lỗi!", "Có lỗi xảy ra trong quá trình gửi tin");
                } catch (InterruptedException ex1) {
                    logger.error("[{}]: ", getUsername(), ex1);
                }
            } finally {
                countDownLatch.countDown();
            }
        }

    }

    private BlockingQueue<String> removeBlacklist(String[] mdns) throws Exception {
        BlockingQueue<String> result = new ArrayBlockingQueue<String>(mdns.length);
        for (String mdn : mdns) {
            result.add(CommonUtil.normalizeMsisdn(mdn.trim().replaceAll("\\r", "")));
        }

        List<BlackList> blackLists = blackListFacade.findAll();
        List<String> blacklistMdns = new ArrayList<String>(blackLists.size());
        for (BlackList blackList : blackLists) {
            blacklistMdns.add(blackList.getMsisdn());
        }

        result.removeAll(blacklistMdns);
        return result;
    }

    public String getMsisdn() {
        return msisdn;
    }

    public void setMsisdn(String msisdn) {
        this.msisdn = msisdn;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

    public String getShortCode() {
        return shortCode;
    }

    public void setShortCode(String shortCode) {
        this.shortCode = shortCode;
    }

}
